package com.whz.job;

import com.whz.generic.spring.SpringUtils;
import com.whz.generic.utils.IpUtils;
import com.whz.job.properties.XxlJobProperties;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import groovy.util.logging.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * xxl-job自动装配
 *
 * @author whz
 */
@Slf4j
@EnableScheduling
@EnableConfigurationProperties(XxlJobProperties.class)
@Configuration(proxyBeanMethods = false)
public class XxlJobAutoConfiguration {

    /**
     * 服务名称 包含 XXL_JOB_ADMIN 则说明是 Admin
     */
    @Value("${generic.job.admin.name:xxl-job-admin}")
    private String jobAdminName;

    @Value("${generic.job.admin.context.path:xxl-job-admin}")
    private String adminContextPath;

    private String lastServers;

    private boolean openCron = false;


    @Bean
    @ConditionalOnMissingBean
    public XxlJobProperties createXxlJobProperties() {
        return new XxlJobProperties();
    }


    /**
     * 配置xxl-job 执行器，提供自动发现 xxl-job-admin 能力
     *
     * @param xxlJobProperties xxl 配置
     * @return XxlJobSpringExecutor
     */
    @Bean
    public XxlJobSpringExecutor xxlJobSpringExecutor(@NonNull XxlJobProperties xxlJobProperties) {
        return buildsSpringExecutor(xxlJobProperties);
    }

    private XxlJobSpringExecutor buildsSpringExecutor(final XxlJobProperties xxlJobProperties) {
        Assert.notNull(xxlJobProperties, "xxlJobProperties is null.");

        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        // 必须有
        xxlJobSpringExecutor.setAppname(xxlJobProperties.getExecutor().getAppName());
        // 必须有
        xxlJobSpringExecutor.setAddress(xxlJobProperties.getExecutor().getAddress());
        // 以下为可选
        xxlJobSpringExecutor.setIp(getIpAddress());
        xxlJobSpringExecutor.setPort(xxlJobProperties.getExecutor().getPort());
        xxlJobSpringExecutor.setAccessToken(xxlJobProperties.getExecutor().getAccessToken());
        xxlJobSpringExecutor.setLogPath(xxlJobProperties.getExecutor().getLogPath());
        xxlJobSpringExecutor.setLogRetentionDays(xxlJobProperties.getExecutor().getLogRetentionDays());

        // 如果配置为空则获取注册中心的服务列表 "http://xxl-job-admin-ip:9080/xxl-job-admin"
        if (!StringUtils.hasText(xxlJobProperties.getAdmin().getAddresses())) {
            String serverList = lastServers = getServers();
            xxlJobSpringExecutor.setAdminAddresses(serverList);
            // 开始定时
            openCron = true;
        } else {
            xxlJobSpringExecutor.setAdminAddresses(xxlJobProperties.getAdmin().getAddresses());
        }

        return xxlJobSpringExecutor;
    }

    private static String getIpAddress() {
        return SpringUtils.getBean(InetUtils.class)
                          .map(net -> net.findFirstNonLoopbackHostInfo().getIpAddress())
                          .orElse(IpUtils.getLocalAddress().getHostAddress());
    }

    @Scheduled(cron = "*/30 * * * * *")
    public void refreshServers() {
        if (!openCron) {
            return;
        }
        String servers = getServers();
        if (!Objects.deepEquals(servers, lastServers)) {
            lastServers = servers;
            final Optional<XxlJobProperties> bean = SpringUtils.getBean(XxlJobProperties.class);
            if (bean.isPresent()) {
                SpringUtils.getBean(XxlJobSpringExecutor.class).ifPresent(oldExecutor -> {
                    SpringUtils.getApplicationContext().ifPresent(applicationContext -> {
                        String[] name = applicationContext.getBeanNamesForType(XxlJobSpringExecutor.class);
                        final AutowireCapableBeanFactory factory = applicationContext.getAutowireCapableBeanFactory();
                        // 关闭旧端口
                        factory.destroyBean(oldExecutor);
                        oldExecutor.setAdminAddresses(servers);
                        factory.initializeBean(oldExecutor, name.length > 0? name[0] : "xxlJobSpringExecutor");
                        // 开启新端口
                        oldExecutor.afterSingletonsInstantiated();
                    });

                });
            }
        }
    }

    private String getServers() {
        return SpringUtils.getBean(DiscoveryClient.class)
                          .map(client -> client.getServices().stream()
                                               .filter(s -> s.contains(jobAdminName))
                                               .flatMap(s -> client.getInstances(s).stream())
                                               .map(instance -> String.format(
                                                       "http://%s:%s/%s",
                                                       instance.getHost(),
                                                       instance.getPort(),
                                                       adminContextPath))
                                               .collect(Collectors.joining(",")))
                          .orElse("");

    }
}